// System includes
#include <QDebug>
#include <QPaintEngine>
#include <QtOpenGL>
#include <QReadLocker>
#include <math.h>

// Local includes
#include "yiggraphicsscene.h"
#include "gui/yignetworkchannelview.h".h"
#include "graphics/yigbasegraphic.h"
#include "graphics/yigsynthgraphic.h"
#include "graphics/yigcolorkit.h"

#define SKIP 0

const qreal YigGraphicsScene::PI = 3.141592654;

YigGraphicsScene::YigGraphicsScene(QObject *parent) :
    QGraphicsScene(parent)
{
    currentBuffers = new QHash<int, SndBuf*>();

    //lastNumberOfCollidingItems = 0;
    //collisionTimer.setInterval(200);
    //QObject::connect(&collisionTimer, SIGNAL(timeout()), this, SLOT(checkNumberOfCollidingItems()));

    QObject::connect(&foregroundFpsTimer, SIGNAL(timeout()), this, SLOT(drawForegroundFrame()), Qt::QueuedConnection);
    //QObject::connect(&backgroundFpsTimer, SIGNAL(timeout()), this, SLOT(drawBackgroundFrame()), Qt::QueuedConnection);
    QObject::connect(&bufferTimer, SIGNAL(timeout()), this, SLOT(prepareBufferRequest()), Qt::QueuedConnection);
    //QObject::connect(&bufferTimer, SIGNAL(timeout()), this, SLOT(prepareBufferRequest()));
    frame = 0;
    bufferToFill = 0;
    foregroundFpsTimer.start(86);
    //backgroundFpsTimer.start(86);
    bufferTimer.start(86);
    displayListsInitialized = false;
}

YigGraphicsScene::~YigGraphicsScene()
{
    foreach(int key, currentBuffers->keys())
    {
        lock.lockForWrite();
        delete[] (float*) currentBuffers->value(key)->data;
        currentBuffers->value(key)->data = 0;
        delete currentBuffers->value(key);
        currentBuffers->insert(key, 0);
        lock.unlock();
        //free(currentBuffers->value(key)->data);
        //delete currentBuffers->value(key);
    }
    currentBuffers->clear();
    delete currentBuffers;
    glDeleteLists(audioArrowIndex, 1);
    glDeleteLists(modArrowIndex, 1);
}

void YigGraphicsScene::setServerOptions(sc::ServerOptions _options)
{
    options = _options;
}

void YigGraphicsScene::addSynthBuffer(int index)
{
    if(!currentBuffers->contains(index))
    {
        /*
        AudioBuffer* scBuffer = new AudioBuffer;
        scBuffer->mNumberChannels = 1;
        scBuffer->mDataByteSize = sizeof(float) * options.bufferSize * options.numAudioBusChannels;
        scBuffer->mData = (void*) malloc(sizeof(float) * options.bufferSize);
        memset(scBuffer->mData, 0, sizeof(float) * options.bufferSize);*/
        //SndBuf* buf = new SndBuf();
        //memset(buf, 0, sizeof(SndBuf*));
        //qDebug() << "BUFFER ADDED TO INDEX: " << index;
        SndBuf* scBuffer = new SndBuf;
        scBuffer->samplerate = options.mPreferredSampleRate;
        scBuffer->sampledur = 1 / options.mPreferredSampleRate;
        scBuffer->channels = 1;
        scBuffer->samples = scBuffer->channels * options.mBufLength;
        scBuffer->frames = options.mBufLength;
        scBuffer->data = (float*) malloc(sizeof(float) * options.mBufLength);
        memset(scBuffer->data, 0, sizeof(float) * options.mBufLength);
        currentBuffers->insert(index, scBuffer);
    }
}

void YigGraphicsScene::removeSynthBuffer(int index)
{
    if(currentBuffers->contains(index))
    {
        lock.lockForWrite();
        delete[] (float*) currentBuffers->value(index)->data;
        currentBuffers->value(index)->data = 0;
        delete currentBuffers->value(index);
        currentBuffers->insert(index, 0);
        currentBuffers->remove(index);
        //free(currentBuffers->value(index)->data);
        //delete currentBuffers->take(index);
        lock.unlock();
    }
}

void drawCableArrow(int x, int y, int size = 1)
{
    glBegin(GL_LINE_LOOP);
    glVertex2i(0 + x, 3 * size + y);
    glVertex2i(3 * size + x, size + y);
    glVertex2i(3 * size + x, 0 + y);
    glVertex2i(0 + x, 2 * size + y);
    glVertex2i(-3 * size + x, 0 + y);
    glVertex2i(-3 * size + x, size + y);
    glEnd();
}

void YigGraphicsScene::createArrowDisplayLists()
{
    // create the lists
    audioArrowIndex =  glGenLists(1);
    modArrowIndex = glGenLists(1);

    // compile the audio arrow display list
    glNewList(audioArrowIndex, GL_COMPILE);
    drawCableArrow(-0, 0, AUDIO_ARROW_SIZE);
    glEndList();

    // compile the mod arrow display list
    glNewList(modArrowIndex, GL_COMPILE);
    drawCableArrow(-0, 0, MOD_ARROW_SIZE);
    glEndList();
}

void YigGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect)
{   
    if ((painter->paintEngine()->type() != QPaintEngine::OpenGL2) &&
        (painter->paintEngine()->type() != QPaintEngine::OpenGL))
    {
        qWarning("OpenGLScene: drawBackground needs a "
        "QGLWidget to be set as viewport on the "
        "graphics view");
        return;
    }

    else
    {
        painter->beginNativePainting();

        if(!displayListsInitialized)
        {
            createArrowDisplayLists();
            displayListsInitialized = true;
        }

        //glEnable (GL_LINE_SMOOTH);
        //glEnable (GL_BLEND);
        //glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        //glHint (GL_LINE_SMOOTH_HINT, GL_FASTEST);
        //glLineWidth (2.5);
        //gl::enableDepthWrite();
        //gl::enableDepthRead();

        //glOrtho(0, sceneRect().width(), 0, sceneRect().height(), 0, 0);
        glClearColor(YigColorKit::background.redF(), YigColorKit::background.greenF(), YigColorKit::background.blueF(), 255);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        //glEnable (GL_LINE_SMOOTH);
        //glEnable (GL_BLEND);
        //glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        //glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
        glLineWidth(1);

        ///////////////
        // Draw Cables
        //////////////

        //int pol;
        float offsetRatio;
        //int size;
        GLuint arrowIndex;
        QList<YigCableGraphic*> cables = ((YigNetworkChannelView*)(views().first()))->getCableGraphics();
        for(int i = 0; i < cables.size(); i++)
        {
            if(cables.at(i)->getOutputType() == YigCableGraphic::AUDIO)
            {
                glColor4f(0, 0, 0, 0.85);
                offsetRatio = 0;
                //size = 3;
                arrowIndex = audioArrowIndex;
            }

            else
            {
                glColor4f(YigColorKit::accent.redF(), YigColorKit::accent.greenF(), YigColorKit::accent.blueF(), 0.85);
                offsetRatio = 0.5;
                arrowIndex = modArrowIndex;
            }
            QPointF p1 = cables.at(i)->line().p1();
            QPointF p2 = cables.at(i)->line().p2();
            qreal angle = (atan2(p2.y() - p1.y(), p2.x() - p1.x()) * 180 / PI) - 90;

            if(angle < 0)
            {
                angle += 360;
            }

            //qDebug() << angle;

            //qDebug() << angle;
            //float* data = ((float*)currentBuffers->value(cables.at(i)->getAudioInputIndex())->mData);
            glPushMatrix();
            //glVertex2f(p1.x(), p1.y());
            //glVertex2f(p2.x(), p2.y());

            //glPopMatrix();
            //glPushMatrix();
            //glRotatef(angle, p1.x(), p1.y(), 0);
            //glTranslatef((float)p1.x(), (float)p1.y(), 0);
            /*
            glBegin(GL_LINE_STRIP);
            for(int j = 0; j < cables.at(i)->line().length()/8; j++)
            {
                glVertex2f((data[(frame+j)%options.blockSize] * 10 * pol) + p1.x(), (j*8) + p1.y());
            }
            glEnd();*/
            //glLineWidth((data[frame%options.blockSize] + 1) * 4);

            glTranslatef(p1.x(), p1.y(), 0);
            glRotatef(angle, 0, 0, 1.0f);
            /*
            glBegin(GL_LINES);
            glVertex2f(0, 0);
            glVertex2f(0, cables.at(i)->line().length());
            glEnd();*/

            glPushMatrix();
            glTranslatef(-12, -32 + (offsetRatio * 32) + (frame % 32), 0);
            for(int j = 0; j < (cables.at(i)->line().length() / 32) - 1; j++)
            {
                //drawCableArrow(-12, j * 32 + (offsetRatio * 32) + (frame % 32), size);
                glTranslatef(0, 32, 0);
                glCallList(arrowIndex);

            }
            glPopMatrix();
            glPopMatrix();
        }

        /*
        for(int i = 0; i < items().size(); i++)
        {
            if(items().at(i)->type() == YigBaseGraphic::SynthGraphicType)
            {


                    //glPushMatrix();

                    for(int j = 0; j < options.blockSize; j++)
                    {

                        // Simple linear oscilloscope
                        glVertex2d(pos.x() + 9 + (j*0.5), pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2) - (data[j]*26));

                        basic circle
                        glVertex2f(x + pos.x() + (YigSynthGraphic::ELLIPSE_SIZE/2) , y + pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2));

                        float tx = -y;
                        float ty = x;

                        x += tx * tangetial_factor;
                        y += ty * tangetial_factor;


                        x *= radial_factor;
                        y *= radial_factor;
                        //qreal degInRad = j * (360/options.blockSize) * ;


                        qreal angleX = j * 2 * PI / diameter;
                        qreal angleY = (j - (data[j]*10)) * 2 * PI / diameter;
                        glVertex2d(pos.x() + (YigSynthGraphic::ELLIPSE_SIZE/2) + (cos(angleX) * radius),
                                   pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2) + (sin(angleY) * radius));


                        if(j < (options.blockSize/2))
                        {
                            glVertex2d(pos.x() - 8 + j + (YigSynthGraphic::ELLIPSE_SIZE/2),
                                       pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2) - (data[j]*20));
                        }

                        else{
                            glVertex2d(pos.x() - 8 - j + YigSynthGraphic::ELLIPSE_SIZE,
                                       pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2) - (data[j]*20));
                        }

                        //j += SKIP;
                        glRotatef(j * (360/options.blockSize), pos.x() + (YigSynthGraphic::ELLIPSE_SIZE/2),
                                  pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2), 0);

                    }
                //glPopMatrix();
                //glEnd();
                //lock.unlock();
            }
        }*/

        painter->endNativePainting();
    }
}

void YigGraphicsScene::drawForeground(QPainter *painter, const QRectF &rect)
{
    painter->beginNativePainting();
    glLineWidth (1.0);
    QList<YigSynthGraphic*> synths = ((YigNetworkChannelView*)(views().first()))->getSynthGraphics();

    for(int i = 0; i < synths.size(); i++)
    {
        float* data = ((float*)currentBuffers->value(synths.at(i)->getAudioOutBus())->data);

        QPointF pos = synths.at(i)->scenePos();

        /////////////////////////////
        // Draw Synth Oscilloscopes
        ////////////////////////////
        //glColor3f(0.0, 0.0, 0.0);
        glColor3f(YigColorKit::accent.redF(), YigColorKit::accent.greenF(), YigColorKit::accent.blueF());
        glBegin(GL_LINE_STRIP);
        //int cursor;
        /* horizontal
        for(int j = 0; j < options.blockSize; j++)
        {
            //cursor = (j + frame) % options.blockSize;
            glVertex2d(pos.x() + 9 + (j*0.5), pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2) - (data[j]*26));
        }*/

        // VERTICAL
        //for(int j = 0; j < options.blockSize; j++)
        for(int j = 0; j < options.mBufLength; j++)
        {
            //cursor = (j + frame) % options.blockSize;
            try {
                glVertex2d(pos.x() + (YigSynthGraphic::ELLIPSE_SIZE/2) - (data[j]*26), pos.y() + 9 + (j*0.5));
            } catch(...) {
                glVertex2d(pos.x() + (YigSynthGraphic::ELLIPSE_SIZE/2) - (0*26), pos.y() + 9 + (j*0.5));
            }
        }
        glEnd();
    }

    painter->endNativePainting();

    /*
    if ((painter->paintEngine()->type() != QPaintEngine::OpenGL2) &&
        (painter->paintEngine()->type() != QPaintEngine::OpenGL))
    {
        qWarning("OpenGLScene: drawBackground needs a "
        "QGLWidget to be set as viewport on the "
        "graphics view");
        return;
    }

    else
    {
        painter->beginNativePainting();

        glEnable (GL_LINE_SMOOTH);
        glEnable (GL_BLEND);
        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glHint (GL_LINE_SMOOTH_HINT, GL_FASTEST);
        glLineWidth (2.5);
        //gl::enableDepthWrite();
        //gl::enableDepthRead();

        //glOrtho(0, sceneRect().width(), 0, sceneRect().height(), 0, 0);
        glColor4f(0.0, 0.0, 0.0,0.75);

        for(int i = 0; i < items().size(); i++)
        {
            if(items().at(i)->type() == YigBaseGraphic::SynthGraphicType)
            {
                YigSynthGraphic* synth = ((YigSynthGraphic*) items().at(i));

                lock.lockForRead();
                float* data = ((float*)currentBuffers->value(synth->getAudioOutBus()).mData);

                QPointF pos = items().at(i)->scenePos();

                glBegin(GL_LINE_STRIP);
                    //glPushMatrix();
                    for(int j = 0; j < options.blockSize; j++)
                    {

                        // Simple linear oscilloscope
                        glVertex2d(pos.x() + 9 + (j*0.5), pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2) - (data[j]*26));

                        basic circle
                        glVertex2f(x + pos.x() + (YigSynthGraphic::ELLIPSE_SIZE/2) , y + pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2));

                        float tx = -y;
                        float ty = x;

                        x += tx * tangetial_factor;
                        y += ty * tangetial_factor;


                        x *= radial_factor;
                        y *= radial_factor;
                        //qreal degInRad = j * (360/options.blockSize) * ;


                        qreal angleX = j * 2 * PI / diameter;
                        qreal angleY = (j - (data[j]*10)) * 2 * PI / diameter;
                        glVertex2d(pos.x() + (YigSynthGraphic::ELLIPSE_SIZE/2) + (cos(angleX) * radius),
                                   pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2) + (sin(angleY) * radius));


                        if(j < (options.blockSize/2))
                        {
                            glVertex2d(pos.x() - 8 + j + (YigSynthGraphic::ELLIPSE_SIZE/2),
                                       pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2) - (data[j]*20));
                        }

                        else{
                            glVertex2d(pos.x() - 8 - j + YigSynthGraphic::ELLIPSE_SIZE,
                                       pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2) - (data[j]*20));
                        }

                        //j += SKIP;
                        glRotatef(j * (360/options.blockSize), pos.x() + (YigSynthGraphic::ELLIPSE_SIZE/2),
                                  pos.y() + (YigSynthGraphic::ELLIPSE_SIZE/2), 0);

                    }
                //glPopMatrix();
                glEnd();
                lock.unlock();
            }
        }
        painter->endNativePainting();
    }*/
}

void YigGraphicsScene::toggleGraphicsAnimation(bool animatingGraphics)
{
    if(animatingGraphics)
    {
        foregroundFpsTimer.start(86);
        bufferTimer.start(86);
    }

    else
    {
        foregroundFpsTimer.stop();
        bufferTimer.stop();
    }
}

void YigGraphicsScene::prepareBufferRequest()
{
    if(currentBuffers->size() > 0)
    {
        emit requestAudioBuffers(currentBuffers);
        /*
        bufferToFill = (bufferToFill + 1) % currentBuffers->size();
        QList<int> keys = currentBuffers->keys();
        bufferToFillKey = keys.at(bufferToFill);
        //qDebug() << "FILL BUFFER: " << keys[bufferToFill];
        emit requestAudioBuffer(bufferToFillKey, (currentBuffers->value(bufferToFillKey)));*/
    }
}

void YigGraphicsScene::drawForegroundFrame()
{
    invalidate(sceneRect(), QGraphicsScene::ForegroundLayer | QGraphicsScene::BackgroundLayer);
    frame = (frame + 1) % (int)sceneRect().width();
    //invalidate(sceneRect(), QGraphicsScene::BackgroundLayer);
}

void YigGraphicsScene::drawBackgroundFrame()
{
    frame = (frame + 1) % (int)sceneRect().width();
    invalidate(sceneRect(), QGraphicsScene::BackgroundLayer);
}

//void YigGraphicsScene::checkNumberOfCollidingItems()
//{
    /*
    QList<QGraphicsItem*> tempCollidingItems = collidingItems();
    for(int i = 0; i < tempCollidingItems.size(); i++)
    {
        //if(tempCollidingItems.at(i)->type() != YigBaseGraphic::ModFieldType
    }

    QList<QGraphicsItem*>::const_iterator iter = tempCollidingItems.constBegin();
    //while(iter
    lastNumberOfCollidingItems;*/
//}
